﻿
using Xacml.Core.Context;
using Xacml.Core.Policy;
using System;
using System.Collections;

namespace Xacml.Core.Runtime
{
  public class VariableDefinition : IEvaluable
  {
    private bool _isEvaluated = false;
    private VariableDefinitionElement _variableDefinition;
    private EvaluationValue _value;

    public EvaluationValue Value
    {
      get
      {
        if (!this._isEvaluated)
          throw new EvaluationException("Te variable must be evaluated.");
        return this._value;
      }
    }

    public bool IsEvaluated
    {
      get
      {
        return this._isEvaluated;
      }
    }

    public VariableDefinition(VariableDefinitionElement variableDefinition)
    {
      this._variableDefinition = variableDefinition;
    }

    public EvaluationValue Evaluate(EvaluationContext context)
    {
      context.Trace("Evaluating variable");
      context.AddIndent();
      try
      {
        if (this._variableDefinition.Expression is ApplyElement)
        {
          context.Trace("Apply within condition.");
          this._value = new Apply((ApplyElement) this._variableDefinition.Expression).Evaluate(context);
          context.TraceContextValues();
          return this._value;
        }
        if (this._variableDefinition.Expression is FunctionElement)
          throw new NotImplementedException("pol.FunctionElement");
        if (this._variableDefinition.Expression is VariableReferenceElement)
        {
          VariableReferenceElement referenceElement = this._variableDefinition.Expression as VariableReferenceElement;
          VariableDefinition variableDefinition = context.CurrentPolicy.VariableDefinition[(object) referenceElement.VariableId] as VariableDefinition;
          context.TraceContextValues();
          if (!variableDefinition.IsEvaluated)
            return variableDefinition.Evaluate(context);
          return variableDefinition.Value;
        }
        if (this._variableDefinition.Expression is Xacml.Core.Policy.AttributeValueElementReadWrite)
        {
          context.Trace("Attribute value {0}", (object) this._variableDefinition.Expression.ToString());
          Xacml.Core.Policy.AttributeValueElementReadWrite elementReadWrite = (Xacml.Core.Policy.AttributeValueElementReadWrite) this._variableDefinition.Expression;
          Xacml.Core.Policy.AttributeValueElement attributeValueElement = new Xacml.Core.Policy.AttributeValueElement(elementReadWrite.DataType, elementReadWrite.Contents, elementReadWrite.SchemaVersion);
          this._value = new EvaluationValue(attributeValueElement.GetTypedValue(attributeValueElement.GetType(context), 0), attributeValueElement.GetType(context));
          return this._value;
        }
        if (this._variableDefinition.Expression is AttributeDesignatorBase)
        {
          context.Trace("Processing attribute designator: {0}", (object) this._variableDefinition.Expression.ToString());
          AttributeDesignatorBase attributeDesignatorBase = (AttributeDesignatorBase) this._variableDefinition.Expression;
          BagValue bagValue = context.Engine.Resolve(context, attributeDesignatorBase);
          if (bagValue.BagSize == 0)
          {
            if (this._variableDefinition.Expression is SubjectAttributeDesignatorElement)
            {
              foreach (Xacml.Core.Context.SubjectElement subjectElement in (CollectionBase) context.ContextDocument.Request.Subjects)
              {
                AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) subjectElement);
                if (attribute != null)
                {
                  context.Trace("Adding subject attribute designator: {0}", (object) attribute.ToString());
                  bagValue.Add((object) attribute);
                  break;
                }
              }
            }
            else if (this._variableDefinition.Expression is ResourceAttributeDesignatorElement)
            {
              AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) context.CurrentResource);
              if (attribute != null)
              {
                context.Trace("Adding resource attribute designator {0}", (object) attribute.ToString());
                bagValue.Add((object) attribute);
              }
            }
            else if (this._variableDefinition.Expression is ActionAttributeDesignatorElement)
            {
              AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) context.ContextDocument.Request.Action);
              if (attribute != null)
              {
                context.Trace("Adding action attribute designator {0}", (object) attribute.ToString());
                bagValue.Add((object) attribute);
              }
            }
            else if (this._variableDefinition.Expression is EnvironmentAttributeDesignatorElement)
            {
              AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) context.ContextDocument.Request.Environment);
              if (attribute != null)
              {
                context.Trace("Adding envrironment attribute designator {0}", (object) attribute.ToString());
                bagValue.Add((object) attribute);
              }
            }
          }
          if (bagValue.BagSize == 0 && attributeDesignatorBase.MustBePresent)
          {
            context.Trace("Attribute is missing");
            context.IsMissingAttribute = true;
            this._value = EvaluationValue.Indeterminate;
          }
          else
            this._value = new EvaluationValue((object) bagValue, bagValue.GetType(context));
          return this._value;
        }
        if (!(this._variableDefinition.Expression is AttributeSelectorElement))
          throw new NotSupportedException("internal error");
        context.Trace("Attribute selector");
        try
        {
          AttributeSelectorElement attributeSelector = (AttributeSelectorElement) this._variableDefinition.Expression;
          BagValue bagValue = context.Engine.Resolve(context, attributeSelector);
          if (bagValue.Elements.Count == 0 && attributeSelector.MustBePresent)
          {
            context.Trace("Attribute is missing");
            context.IsMissingAttribute = true;
            this._value = EvaluationValue.Indeterminate;
          }
          else
            this._value = new EvaluationValue((object) bagValue, bagValue.GetType(context));
        }
        catch (EvaluationException ex)
        {
          context.Trace("ERR: {0}", (object) ex.Message);
          context.ProcessingError = true;
          this._value = EvaluationValue.Indeterminate;
        }
        return this._value;
      }
      finally
      {
        this._isEvaluated = true;
        context.RemoveIndent();
      }
    }
  }
}
